use std::{net::SocketAddr, path::PathBuf, sync::Arc};

use axum::{
    extract::{Path as AxumPath, State},
    http::StatusCode,
    routing::get,
    Router,
};
use tower_http::services::ServeDir;
use tracing::{info, warn};

#[derive(Debug)]
struct HttpServState {
    path: PathBuf,
}
pub async fn process_http_server(path: PathBuf, port: u16) -> anyhow::Result<()> {
    let addr = SocketAddr::from(([0, 0, 0, 0], port));
    info!("Serving {:?} on {}", path, addr);
    let sate = HttpServState { path: path.clone() };
    let service = ServeDir::new(path)
        .append_index_html_on_directories(true)
        .precompressed_gzip()
        .precompressed_br()
        .precompressed_deflate()
        .precompressed_zstd();
    let listener = tokio::net::TcpListener::bind(addr).await?;
    let router = Router::new()
        .route("/*path", get(file_handler))
        .nest_service("/tower", service)
        .with_state(Arc::new(sate));
    // info!("path:{:?}")
    info!("http server start at: http://127.0.0.1:{}", port);
    axum::serve(listener, router).await?;
    Ok(())
}

async fn file_handler(
    State(state): State<Arc<HttpServState>>,
    AxumPath(path): AxumPath<String>,
) -> (StatusCode, String) {
    let p = std::path::Path::new(&state.path).join(path);
    info!("Reading file {:?}", p);
    if !p.exists() {
        (
            StatusCode::NOT_FOUND,
            format!("File {} note found", p.display()),
        )
    } else {
        // TODO: test p is a directory
        // if it is a directory, list all files/subdirectories
        // as <li><a href="/path/to/file">file name</a></li>
        // <html><body><ul>...</ul></body></html>
        match tokio::fs::read_to_string(p).await {
            Ok(content) => {
                info!("Read {} bytes", content.len());
                (StatusCode::OK, content)
            }
            Err(e) => {
                warn!("Error reading file: {:?}", e);
                (StatusCode::INTERNAL_SERVER_ERROR, e.to_string())
            }
        }
    }
}

// async fn file_handler(
//     State(state): State<Arc<HttpServState>>,
//     AxumPath(path): AxumPath<String>,
// ) -> (StatusCode, String) {
//     let p = std::path::Path::new(&state.path).join(path);
//     info!("Reading file :{:?}", p);
//     if !p.exists() {
//         (
//             StatusCode::NOT_FOUND,
//             format!("File {:?} not found", p.display()),
//         )
//     } else {
//         if p.is_dir() {
//             let mut html_list = Vec::new();
//             html_list.push("<html lang=\"en\">".to_string());
//             html_list.push("<head>".to_string());
//             html_list.push(
//                 "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
//                     .to_string(),
//             );
//             let title = format!("<title>{}</title>", p.display());
//             html_list.push(title);
//             html_list.push("<body>".to_string());
//             let mut file_list = Vec::new();
//             fn visit_dir(dir: &Path, file_list: &mut Vec<String>) -> anyhow::Result<()> {
//                 if dir.is_dir() {
//                     for entry in fs::read_dir(dir)? {
//                         let entry = entry?;
//                         let path = entry.path();
//                         if path.is_dir() {
//                             let parent: PathBuf = dir.into();
//                             let path = parent.join(path);
//                             visit_dir(&path, file_list)?;
//                         } else {
//                             file_list.push(entry.path().to_str().unwrap().to_string());
//                         }
//                     }
//                 } else {
//                     file_list.push(dir.to_str().unwrap().to_string());
//                 }
//                 Ok(())
//             }
//             visit_dir(&p, &mut file_list).unwrap();
//             for file_name in file_list {
//                 html_list.push(format!("<a href=\"{}\">{} </a>", file_name, file_name));
//             }
//             html_list.push("</body>".to_string());
//             html_list.push("</html>".to_string());
//             let content = html_list.join("\n");
//             info!("content \n {}", &content);
//             (StatusCode::OK, content)
//         } else {
//             match tokio::fs::read_to_string(p).await {
//                 Ok(content) => {
//                     info!("Read {} bytes", content.len());
//                     (StatusCode::OK, content)
//                 }
//                 Err(e) => {
//                     warn!("Error reading file:{:?}", e);
//                     (StatusCode::INTERNAL_SERVER_ERROR, format!("{:?}", e))
//                 }
//             }
//         }
//     }
// }

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_file_handler() {
        let state = Arc::new(HttpServState {
            path: PathBuf::from("."),
        });

        let (status, content) =
            file_handler(State(state), AxumPath("Cargo.toml".to_string())).await;

        assert_eq!(status, StatusCode::OK);
        assert!(content.contains("anyhow = \"1.0\""));
    }
}
